// pathfinder/shaders/tile_fragment.inc.glsl
//
// Copyright © 2020 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//      Mask UV 0         Mask UV 1
//          +                 +
//          |                 |
//    +-----v-----+     +-----v-----+
//    |           | MIN |           |
//    |  Mask  0  +----->  Mask  1  +------+
//    |           |     |           |      |
//    +-----------+     +-----------+      v       +-------------+
//                                       Apply     |             |       GPU
//                                       Mask +---->  Composite  +---->Blender
//                                         ^       |             |
//    +-----------+     +-----------+      |       +-------------+
//    |           |     |           |      |
//    |  Color 0  +----->  Color 1  +------+
//    |  Filter   |  ×  |           |
//    |           |     |           |
//    +-----^-----+     +-----^-----+
//          |                 |
//          +                 +
//     Color UV 0        Color UV 1

#define FRAC_6_PI   1.9098593171027443
#define FRAC_PI_3   1.0471975511965976

#define TILE_CTRL_MASK_MASK                     0x3
#define TILE_CTRL_MASK_WINDING                  0x1
#define TILE_CTRL_MASK_EVEN_ODD                 0x2

#define TILE_CTRL_MASK_0_SHIFT                  0

#define COMBINER_CTRL_COLOR_COMBINE_MASK        0x3
#define COMBINER_CTRL_COLOR_COMBINE_SRC_IN      0x1
#define COMBINER_CTRL_COLOR_COMBINE_DEST_IN     0x2

#define COMBINER_CTRL_FILTER_MASK               0xf
#define COMBINER_CTRL_FILTER_RADIAL_GRADIENT    0x1
#define COMBINER_CTRL_FILTER_TEXT               0x2
#define COMBINER_CTRL_FILTER_BLUR               0x3
#define COMBINER_CTRL_FILTER_COLOR_MATRIX       0x4

#define COMBINER_CTRL_COMPOSITE_MASK            0xf
#define COMBINER_CTRL_COMPOSITE_NORMAL          0x0
#define COMBINER_CTRL_COMPOSITE_MULTIPLY        0x1
#define COMBINER_CTRL_COMPOSITE_SCREEN          0x2
#define COMBINER_CTRL_COMPOSITE_OVERLAY         0x3
#define COMBINER_CTRL_COMPOSITE_DARKEN          0x4
#define COMBINER_CTRL_COMPOSITE_LIGHTEN         0x5
#define COMBINER_CTRL_COMPOSITE_COLOR_DODGE     0x6
#define COMBINER_CTRL_COMPOSITE_COLOR_BURN      0x7
#define COMBINER_CTRL_COMPOSITE_HARD_LIGHT      0x8
#define COMBINER_CTRL_COMPOSITE_SOFT_LIGHT      0x9
#define COMBINER_CTRL_COMPOSITE_DIFFERENCE      0xa
#define COMBINER_CTRL_COMPOSITE_EXCLUSION       0xb
#define COMBINER_CTRL_COMPOSITE_HUE             0xc
#define COMBINER_CTRL_COMPOSITE_SATURATION      0xd
#define COMBINER_CTRL_COMPOSITE_COLOR           0xe
#define COMBINER_CTRL_COMPOSITE_LUMINOSITY      0xf

#define COMBINER_CTRL_COLOR_FILTER_SHIFT        4
#define COMBINER_CTRL_COLOR_COMBINE_SHIFT       8
#define COMBINER_CTRL_COMPOSITE_SHIFT          10

// Color sampling

vec4 sampleColor(sampler2D colorTexture, vec2 colorTexCoord) {
    return texture(colorTexture, colorTexCoord);
}

// Color combining

vec4 combineColor0(vec4 destColor, vec4 srcColor, int op) {
    switch (op) {
    case COMBINER_CTRL_COLOR_COMBINE_SRC_IN:
        return vec4(srcColor.rgb, srcColor.a * destColor.a);
    case COMBINER_CTRL_COLOR_COMBINE_DEST_IN:
        return vec4(destColor.rgb, srcColor.a * destColor.a);
    }
    return destColor;
}

// Text filter

float filterTextSample1Tap(float offset, sampler2D colorTexture, vec2 colorTexCoord) {
    return texture(colorTexture, colorTexCoord + vec2(offset, 0.0)).r;
}

// Samples 9 taps around the current pixel.
void filterTextSample9Tap(out vec4 outAlphaLeft,
                          out float outAlphaCenter,
                          out vec4 outAlphaRight,
                          sampler2D colorTexture,
                          vec2 colorTexCoord,
                          vec4 kernel,
                          float onePixel) {
    bool wide = kernel.x > 0.0;
    outAlphaLeft =
        vec4(wide ? filterTextSample1Tap(-4.0 * onePixel, colorTexture, colorTexCoord) : 0.0,
             filterTextSample1Tap(-3.0 * onePixel, colorTexture, colorTexCoord),
             filterTextSample1Tap(-2.0 * onePixel, colorTexture, colorTexCoord),
             filterTextSample1Tap(-1.0 * onePixel, colorTexture, colorTexCoord));
    outAlphaCenter = filterTextSample1Tap(0.0, colorTexture, colorTexCoord);
    outAlphaRight =
        vec4(filterTextSample1Tap(1.0 * onePixel, colorTexture, colorTexCoord),
             filterTextSample1Tap(2.0 * onePixel, colorTexture, colorTexCoord),
             filterTextSample1Tap(3.0 * onePixel, colorTexture, colorTexCoord),
             wide ? filterTextSample1Tap(4.0 * onePixel, colorTexture, colorTexCoord) : 0.0);
}

float filterTextConvolve7Tap(vec4 alpha0, vec3 alpha1, vec4 kernel) {
    return dot(alpha0, kernel) + dot(alpha1, kernel.zyx);
}

float filterTextGammaCorrectChannel(float bgColor, float fgColor, sampler2D gammaLUT) {
    return texture(gammaLUT, vec2(fgColor, 1.0 - bgColor)).r;
}

// `fgColor` is in linear space.
vec3 filterTextGammaCorrect(vec3 bgColor, vec3 fgColor, sampler2D gammaLUT) {
    return vec3(filterTextGammaCorrectChannel(bgColor.r, fgColor.r, gammaLUT),
                filterTextGammaCorrectChannel(bgColor.g, fgColor.g, gammaLUT),
                filterTextGammaCorrectChannel(bgColor.b, fgColor.b, gammaLUT));
}

//                | x          y          z          w
//  --------------+--------------------------------------------------------
//  filterParams0 | kernel[0]  kernel[1]  kernel[2]  kernel[3]
//  filterParams1 | bgColor.r  bgColor.g  bgColor.b  -
//  filterParams2 | fgColor.r  fgColor.g  fgColor.b  gammaCorrectionEnabled
vec4 filterText(vec2 colorTexCoord,
                sampler2D colorTexture,
                sampler2D gammaLUT,
                vec2 colorTextureSize,
                vec4 filterParams0,
                vec4 filterParams1,
                vec4 filterParams2) {
    // Unpack.
    vec4 kernel = filterParams0;
    vec3 bgColor = filterParams1.rgb;
    vec3 fgColor = filterParams2.rgb;
    bool gammaCorrectionEnabled = filterParams2.a != 0.0;

    // Apply defringing if necessary.
    vec3 alpha;
    if (kernel.w == 0.0) {
        alpha = texture(colorTexture, colorTexCoord).rrr;
    } else {
        vec4 alphaLeft, alphaRight;
        float alphaCenter;
        filterTextSample9Tap(alphaLeft,
                             alphaCenter,
                             alphaRight,
                             colorTexture,
                             colorTexCoord,
                             kernel,
                             1.0 / colorTextureSize.x);

        float r = filterTextConvolve7Tap(alphaLeft, vec3(alphaCenter, alphaRight.xy), kernel);
        float g = filterTextConvolve7Tap(vec4(alphaLeft.yzw, alphaCenter), alphaRight.xyz, kernel);
        float b = filterTextConvolve7Tap(vec4(alphaLeft.zw, alphaCenter, alphaRight.x),
                                         alphaRight.yzw,
                                         kernel);

        alpha = vec3(r, g, b);
    }

    // Apply gamma correction if necessary.
    if (gammaCorrectionEnabled)
        alpha = filterTextGammaCorrect(bgColor, alpha, gammaLUT);

    // Finish.
    return vec4(mix(bgColor, fgColor, alpha), 1.0);
}

// Other filters

// This is based on Pixman (MIT license). Copy and pasting the excellent comment
// from there:

// Implementation of radial gradients following the PDF specification.
// See section 8.7.4.5.4 Type 3 (Radial) Shadings of the PDF Reference
// Manual (PDF 32000-1:2008 at the time of this writing).
//
// In the radial gradient problem we are given two circles (c₁,r₁) and
// (c₂,r₂) that define the gradient itself.
//
// Mathematically the gradient can be defined as the family of circles
//
//     ((1-t)·c₁ + t·(c₂), (1-t)·r₁ + t·r₂)
//
// excluding those circles whose radius would be < 0. When a point
// belongs to more than one circle, the one with a bigger t is the only
// one that contributes to its color. When a point does not belong
// to any of the circles, it is transparent black, i.e. RGBA (0, 0, 0, 0).
// Further limitations on the range of values for t are imposed when
// the gradient is not repeated, namely t must belong to [0,1].
//
// The graphical result is the same as drawing the valid (radius > 0)
// circles with increasing t in [-∞, +∞] (or in [0,1] if the gradient
// is not repeated) using SOURCE operator composition.
//
// It looks like a cone pointing towards the viewer if the ending circle
// is smaller than the starting one, a cone pointing inside the page if
// the starting circle is the smaller one and like a cylinder if they
// have the same radius.
//
// What we actually do is, given the point whose color we are interested
// in, compute the t values for that point, solving for t in:
//
//     length((1-t)·c₁ + t·(c₂) - p) = (1-t)·r₁ + t·r₂
//
// Let's rewrite it in a simpler way, by defining some auxiliary
// variables:
//
//     cd = c₂ - c₁
//     pd = p - c₁
//     dr = r₂ - r₁
//     length(t·cd - pd) = r₁ + t·dr
//
// which actually means
//
//     hypot(t·cdx - pdx, t·cdy - pdy) = r₁ + t·dr
//
// or
//
//     ⎷((t·cdx - pdx)² + (t·cdy - pdy)²) = r₁ + t·dr.
//
// If we impose (as stated earlier) that r₁ + t·dr ≥ 0, it becomes:
//
//     (t·cdx - pdx)² + (t·cdy - pdy)² = (r₁ + t·dr)²
//
// where we can actually expand the squares and solve for t:
//
//     t²cdx² - 2t·cdx·pdx + pdx² + t²cdy² - 2t·cdy·pdy + pdy² =
//       = r₁² + 2·r₁·t·dr + t²·dr²
//
//     (cdx² + cdy² - dr²)t² - 2(cdx·pdx + cdy·pdy + r₁·dr)t +
//         (pdx² + pdy² - r₁²) = 0
//
//     A = cdx² + cdy² - dr²
//     B = pdx·cdx + pdy·cdy + r₁·dr
//     C = pdx² + pdy² - r₁²
//     At² - 2Bt + C = 0
//
// The solutions (unless the equation degenerates because of A = 0) are:
//
//     t = (B ± ⎷(B² - A·C)) / A
//
// The solution we are going to prefer is the bigger one, unless the
// radius associated to it is negative (or it falls outside the valid t
// range).
//
// Additional observations (useful for optimizations):
// A does not depend on p
//
// A < 0 ⟺ one of the two circles completely contains the other one
//   ⟺ for every p, the radii associated with the two t solutions have
//       opposite sign
//
//                | x           y           z               w
//  --------------+-----------------------------------------------------
//  filterParams0 | lineFrom.x  lineFrom.y  lineVector.x    lineVector.y
//  filterParams1 | radii.x     radii.y     uvOrigin.x      uvOrigin.y
//  filterParams2 | -           -           -               -
vec4 filterRadialGradient(vec2 colorTexCoord,
                          sampler2D colorTexture,
                          vec2 colorTextureSize,
                          vec2 fragCoord,
                          vec2 framebufferSize,
                          vec4 filterParams0,
                          vec4 filterParams1) {
    vec2 lineFrom = filterParams0.xy, lineVector = filterParams0.zw;
    vec2 radii = filterParams1.xy, uvOrigin = filterParams1.zw;

    vec2 dP = colorTexCoord - lineFrom, dC = lineVector;
    float dR = radii.y - radii.x;

    float a = dot(dC, dC) - dR * dR;
    float b = dot(dP, dC) + radii.x * dR;
    float c = dot(dP, dP) - radii.x * radii.x;
    float discrim = b * b - a * c;

    vec4 color = vec4(0.0);
    if (discrim != 0.0) {
        vec2 ts = vec2(sqrt(discrim) * vec2(1.0, -1.0) + vec2(b)) / vec2(a);
        if (ts.x > ts.y)
            ts = ts.yx;
        float t = ts.x >= 0.0 ? ts.x : ts.y;
        color = texture(colorTexture, uvOrigin + vec2(t, 0.0));
    }

    return color;
}

//                | x             y             z             w
//  --------------+----------------------------------------------------
//  filterParams0 | srcOffset.x   srcOffset.y   support       -
//  filterParams1 | gaussCoeff.x  gaussCoeff.y  gaussCoeff.z  -
//  filterParams2 | -             -                 -             -
vec4 filterBlur(vec2 colorTexCoord,
                sampler2D colorTexture,
                vec2 colorTextureSize,
                vec4 filterParams0,
                vec4 filterParams1) {
    // Unpack.
    vec2 srcOffsetScale = filterParams0.xy / colorTextureSize;
    int support = int(filterParams0.z);
    vec3 gaussCoeff = filterParams1.xyz;

    // Set up our incremental calculation.
    float gaussSum = gaussCoeff.x;
    vec4 color = texture(colorTexture, colorTexCoord) * gaussCoeff.x;
    gaussCoeff.xy *= gaussCoeff.yz;

    // This is a common trick that lets us use the texture filtering hardware to evaluate two
    // texels at a time. The basic principle is that, if c0 and c1 are colors of adjacent texels
    // and k0 and k1 are arbitrary factors, the formula `k0 * c0 + k1 * c1` is equivalent to
    // `(k0 + k1) * lerp(c0, c1, k1 / (k0 + k1))`. Linear interpolation, as performed by the
    // texturing hardware when sampling adjacent pixels in one direction, evaluates
    // `lerp(c0, c1, t)` where t is the offset from the texel with color `c0`. To evaluate the
    // formula `k0 * c0 + k1 * c1`, therefore, we can use the texture hardware to perform linear
    // interpolation with `t = k1 / (k0 + k1)`.
    for (int i = 1; i <= support; i += 2) {
        float gaussPartialSum = gaussCoeff.x;
        gaussCoeff.xy *= gaussCoeff.yz;
        gaussPartialSum += gaussCoeff.x;

        vec2 srcOffset = srcOffsetScale * (float(i) + gaussCoeff.x / gaussPartialSum);
        color += (texture(colorTexture, colorTexCoord - srcOffset) +
                  texture(colorTexture, colorTexCoord + srcOffset)) * gaussPartialSum;

        gaussSum += 2.0 * gaussPartialSum;
        gaussCoeff.xy *= gaussCoeff.yz;
    }

    // Finish.
    return color / gaussSum;
}

vec4 filterColorMatrix(vec2 colorTexCoord,
                       sampler2D colorTexture,
                       vec4 filterParams0,
                       vec4 filterParams1,
                       vec4 filterParams2,
                       vec4 filterParams3,
                       vec4 filterParams4) {
    vec4 srcColor = texture(colorTexture, colorTexCoord);
    mat4 colorMatrix = mat4(filterParams0, filterParams1, filterParams2, filterParams3);
    return colorMatrix * srcColor + filterParams4;
}

vec4 filterNone(vec2 colorTexCoord, sampler2D colorTexture) {
    return sampleColor(colorTexture, colorTexCoord);
}

vec4 filterColor(vec2 colorTexCoord,
                 sampler2D colorTexture,
                 sampler2D gammaLUT,
                 vec2 colorTextureSize,
                 vec2 fragCoord,
                 vec2 framebufferSize,
                 vec4 filterParams0,
                 vec4 filterParams1,
                 vec4 filterParams2,
                 vec4 filterParams3,
                 vec4 filterParams4,
                 int colorFilter) {
    switch (colorFilter) {
    case COMBINER_CTRL_FILTER_RADIAL_GRADIENT:
        return filterRadialGradient(colorTexCoord,
                                    colorTexture,
                                    colorTextureSize,
                                    fragCoord,
                                    framebufferSize,
                                    filterParams0,
                                    filterParams1);
    case COMBINER_CTRL_FILTER_BLUR:
        return filterBlur(colorTexCoord,
                          colorTexture,
                          colorTextureSize,
                          filterParams0,
                          filterParams1);
    case COMBINER_CTRL_FILTER_TEXT:
        return filterText(colorTexCoord,
                          colorTexture,
                          gammaLUT,
                          colorTextureSize,
                          filterParams0,
                          filterParams1,
                          filterParams2);
    case COMBINER_CTRL_FILTER_COLOR_MATRIX:
        return filterColorMatrix(colorTexCoord,
                          colorTexture,
                          filterParams0,
                          filterParams1,
                          filterParams2,
                          filterParams3,
                          filterParams4);
    }
    return filterNone(colorTexCoord, colorTexture);
}

// Compositing

vec3 compositeSelect(bvec3 cond, vec3 ifTrue, vec3 ifFalse) {
    return vec3(cond.x ? ifTrue.x : ifFalse.x,
                cond.y ? ifTrue.y : ifFalse.y,
                cond.z ? ifTrue.z : ifFalse.z);
}

float compositeDivide(float num, float denom) {
    return denom != 0.0 ? num / denom : 0.0;
}

vec3 compositeColorDodge(vec3 destColor, vec3 srcColor) {
    bvec3 destZero = equal(destColor, vec3(0.0)), srcOne = equal(srcColor, vec3(1.0));
    return compositeSelect(destZero,
                           vec3(0.0),
                           compositeSelect(srcOne, vec3(1.0), destColor / (vec3(1.0) - srcColor)));
}

// https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB_alternative
vec3 compositeHSLToRGB(vec3 hsl) {
    float a = hsl.y * min(hsl.z, 1.0 - hsl.z);
    vec3 ks = mod(vec3(0.0, 8.0, 4.0) + vec3(hsl.x * FRAC_6_PI), 12.0);
    return hsl.zzz - clamp(min(ks - vec3(3.0), vec3(9.0) - ks), -1.0, 1.0) * a;
}

// https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
vec3 compositeRGBToHSL(vec3 rgb) {
    float v = max(max(rgb.r, rgb.g), rgb.b), xMin = min(min(rgb.r, rgb.g), rgb.b);
    float c = v - xMin, l = mix(xMin, v, 0.5);
    vec3 terms = rgb.r == v ? vec3(0.0, rgb.gb) :
                 rgb.g == v ? vec3(2.0, rgb.br) :
                              vec3(4.0, rgb.rg);
    float h = FRAC_PI_3 * compositeDivide(terms.x * c + terms.y - terms.z, c);
    float s = compositeDivide(c, v);
    return vec3(h, s, l);
}

vec3 compositeScreen(vec3 destColor, vec3 srcColor) {
    return destColor + srcColor - destColor * srcColor;
}

vec3 compositeHardLight(vec3 destColor, vec3 srcColor) {
    return compositeSelect(lessThanEqual(srcColor, vec3(0.5)),
                           destColor * vec3(2.0) * srcColor,
                           compositeScreen(destColor, vec3(2.0) * srcColor - vec3(1.0)));
}

vec3 compositeSoftLight(vec3 destColor, vec3 srcColor) {
    vec3 darkenedDestColor =
        compositeSelect(lessThanEqual(destColor, vec3(0.25)),
                        ((vec3(16.0) * destColor - 12.0) * destColor + 4.0) * destColor,
                        sqrt(destColor));
    vec3 factor = compositeSelect(lessThanEqual(srcColor, vec3(0.5)),
                                  destColor * (vec3(1.0) - destColor),
                                  darkenedDestColor - destColor);
    return destColor + (srcColor * 2.0 - 1.0) * factor;
}

vec3 compositeHSL(vec3 destColor, vec3 srcColor, int op) {
    switch (op) {
    case COMBINER_CTRL_COMPOSITE_HUE:
        return vec3(srcColor.x,  destColor.y, destColor.z);
    case COMBINER_CTRL_COMPOSITE_SATURATION:
        return vec3(destColor.x, srcColor.y,  destColor.z);
    case COMBINER_CTRL_COMPOSITE_COLOR:
        return vec3(srcColor.x,  srcColor.y,  destColor.z);
    default:
        return vec3(destColor.x, destColor.y, srcColor.z);
    }
}

vec3 compositeRGB(vec3 destColor, vec3 srcColor, int op) {
    switch (op) {
    case COMBINER_CTRL_COMPOSITE_MULTIPLY:
        return destColor * srcColor;
    case COMBINER_CTRL_COMPOSITE_SCREEN:
        return compositeScreen(destColor, srcColor);
    case COMBINER_CTRL_COMPOSITE_OVERLAY:
        return compositeHardLight(srcColor, destColor);
    case COMBINER_CTRL_COMPOSITE_DARKEN:
        return min(destColor, srcColor);
    case COMBINER_CTRL_COMPOSITE_LIGHTEN:
        return max(destColor, srcColor);
    case COMBINER_CTRL_COMPOSITE_COLOR_DODGE:
        return compositeColorDodge(destColor, srcColor);
    case COMBINER_CTRL_COMPOSITE_COLOR_BURN:
        return vec3(1.0) - compositeColorDodge(vec3(1.0) - destColor, vec3(1.0) - srcColor);
    case COMBINER_CTRL_COMPOSITE_HARD_LIGHT:
        return compositeHardLight(destColor, srcColor);
    case COMBINER_CTRL_COMPOSITE_SOFT_LIGHT:
        return compositeSoftLight(destColor, srcColor);
    case COMBINER_CTRL_COMPOSITE_DIFFERENCE:
        return abs(destColor - srcColor);
    case COMBINER_CTRL_COMPOSITE_EXCLUSION:
        return destColor + srcColor - vec3(2.0) * destColor * srcColor;
    case COMBINER_CTRL_COMPOSITE_HUE:
    case COMBINER_CTRL_COMPOSITE_SATURATION:
    case COMBINER_CTRL_COMPOSITE_COLOR:
    case COMBINER_CTRL_COMPOSITE_LUMINOSITY:
        return compositeHSLToRGB(compositeHSL(compositeRGBToHSL(destColor),
                                              compositeRGBToHSL(srcColor),
                                              op));
    }
    return srcColor;
}

vec4 composite(vec4 srcColor,
               sampler2D destTexture,
               vec2 destTextureSize,
               vec2 fragCoord,
               int op) {
    if (op == COMBINER_CTRL_COMPOSITE_NORMAL)
        return srcColor;

    // FIXME(pcwalton): What should the output alpha be here?
    vec2 destTexCoord = fragCoord / destTextureSize;
    vec4 destColor = texture(destTexture, destTexCoord);
    vec3 blendedRGB = compositeRGB(destColor.rgb, srcColor.rgb, op);
    return vec4(srcColor.a * (1.0 - destColor.a) * srcColor.rgb +
                srcColor.a * destColor.a * blendedRGB +
                (1.0 - srcColor.a) * destColor.rgb,
                1.0);
}

// Masks

float sampleMask(float maskAlpha,
                 sampler2D maskTexture,
                 vec2 maskTextureSize,
                 vec3 maskTexCoord,
                 int maskCtrl) {
    if (maskCtrl == 0)
        return maskAlpha;

    ivec2 maskTexCoordI = ivec2(floor(maskTexCoord.xy));
    vec4 texel = texture(maskTexture, (vec2(maskTexCoordI / ivec2(1, 4)) + 0.5) / maskTextureSize);
    float coverage = texel[maskTexCoordI.y % 4] + maskTexCoord.z;

    if ((maskCtrl & TILE_CTRL_MASK_WINDING) != 0)
        coverage = abs(coverage);
    else
        coverage = 1.0 - abs(1.0 - mod(coverage, 2.0));
    return min(maskAlpha, coverage);
}

// Main function

vec4 calculateColor(vec2 fragCoord,
                    sampler2D colorTexture0,
                    sampler2D maskTexture0,
                    sampler2D destTexture,
                    sampler2D gammaLUT,
                    vec2 colorTextureSize0,
                    vec2 maskTextureSize0,
                    vec4 filterParams0,
                    vec4 filterParams1,
                    vec4 filterParams2,
                    vec4 filterParams3,
                    vec4 filterParams4,
                    vec2 framebufferSize,
                    int ctrl,
                    vec3 maskTexCoord0,
                    vec2 colorTexCoord0,
                    vec4 baseColor,
                    int tileCtrl) {
    // Sample mask.
    int maskCtrl0 = (tileCtrl >> TILE_CTRL_MASK_0_SHIFT) & TILE_CTRL_MASK_MASK;
    float maskAlpha = 1.0;
    maskAlpha = sampleMask(maskAlpha, maskTexture0, maskTextureSize0, maskTexCoord0, maskCtrl0);

    // Sample color.
    vec4 color = baseColor;
    int color0Combine = (ctrl >> COMBINER_CTRL_COLOR_COMBINE_SHIFT) &
        COMBINER_CTRL_COLOR_COMBINE_MASK;
    if (color0Combine != 0) {
        int color0Filter = (ctrl >> COMBINER_CTRL_COLOR_FILTER_SHIFT) & COMBINER_CTRL_FILTER_MASK;
        vec4 color0 = filterColor(colorTexCoord0,
                                  colorTexture0,
                                  gammaLUT,
                                  colorTextureSize0,
                                  fragCoord,
                                  framebufferSize,
                                  filterParams0,
                                  filterParams1,
                                  filterParams2,
                                  filterParams3,
                                  filterParams4,
                                  color0Filter);
        color = combineColor0(color, color0, color0Combine);
    }

    // Apply mask.
    color.a *= maskAlpha;

    // Apply composite.
    int compositeOp = (ctrl >> COMBINER_CTRL_COMPOSITE_SHIFT) & COMBINER_CTRL_COMPOSITE_MASK;
    color = composite(color, destTexture, framebufferSize, fragCoord, compositeOp);

    // Premultiply alpha.
    color.rgb *= color.a;
    return color;
}
